/**
 * 
 */
package com.huxi.lang.io;

import static java.nio.charset.StandardCharsets.UTF_16BE;
import static java.nio.charset.StandardCharsets.UTF_16LE;
import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;

import org.apache.commons.io.ByteOrderMark;
import org.apache.commons.io.input.BOMInputStream;

/**
 * 常用文件IO操作工具类
 * 
 * @author jian.wu
 * @since 1.0
 */
public abstract class IOUtils {

	public static final Charset UTF_32LE = Charset.forName("UTF-32LE");
	public static final Charset UTF_32BE = Charset.forName("UTF-32BE");

	/**
	 * 字节流转字符流且自动探测UTF*编码
	 * 
	 * @param in      字节流，非空。
	 * @param charset 字符编码，默认UTF-8编码。
	 * @return 字符流，非空。
	 * @throws IOException          字节读取失败
	 * @throws NullPointerException 字节流编码
	 * @since 1.0
	 */
	public static InputStreamReader openInputStreamReaderAutoDetectCharset(InputStream in, Charset charset)
			throws IOException {
		Objects.requireNonNull(in);
		BOMInputStream bomIn = new BOMInputStream(in, ByteOrderMark.UTF_8, ByteOrderMark.UTF_16LE,
				ByteOrderMark.UTF_16BE, ByteOrderMark.UTF_32LE, ByteOrderMark.UTF_32BE);
		if (bomIn.hasBOM(ByteOrderMark.UTF_8)) {
			charset = UTF_8;
		} else if (bomIn.hasBOM(ByteOrderMark.UTF_16LE)) {
			charset = UTF_16LE;
		} else if (bomIn.hasBOM(ByteOrderMark.UTF_16BE)) {
			charset = UTF_16BE;
		} else if (bomIn.hasBOM(ByteOrderMark.UTF_32LE)) {
			charset = UTF_32LE;
		} else if (bomIn.hasBOM(ByteOrderMark.UTF_32BE)) {
			charset = UTF_32BE;
		} else if (charset == null) {
			charset = UTF_8;
		}
		return new InputStreamReader(bomIn, charset);
	}

	/**
	 * 字节流转字符流且字节流头部自动写入UTF*编码
	 * 
	 * @param out     字节流，非空。
	 * @param charset 字符编码，默认UTF-8编码。
	 * @return 字符流，非空。
	 * @throws IOException          字节写入失败
	 * @throws NullPointerException 字节流编码
	 * @since 1.0
	 */
	public static OutputStreamWriter openOuterStreamWriter(OutputStream out, Charset charset) throws IOException {
		Objects.requireNonNull(out);
		if (charset == null) {
			charset = UTF_8;
		}
		if (charset.equals(UTF_8)) {
			out.write(ByteOrderMark.UTF_8.getBytes());
		} else if (charset.equals(UTF_16LE)) {
			out.write(ByteOrderMark.UTF_16LE.getBytes());
		} else if (charset.equals(UTF_16BE)) {
			out.write(ByteOrderMark.UTF_16BE.getBytes());
		} else if (charset.equals(UTF_32LE)) {
			out.write(ByteOrderMark.UTF_32LE.getBytes());
		} else if (charset.equals(UTF_32BE)) {
			out.write(ByteOrderMark.UTF_32BE.getBytes());
		}
		return new OutputStreamWriter(out, charset);
	}

	/**
	 * 读取文件文本内容
	 * 
	 * @param path    文件路径，非空。
	 * @param charset 字符集
	 * @return 文件内容，非空值。
	 * @throws IOException 读取失败
	 */
	public static String getText(String path, Charset charset) throws IOException {
		if (path == null || path.length() == 0) {
			throw new IllegalArgumentException("文件路径参数不能为空");
		}
		return getText(new FileInputStream(path), charset);
	}

	/**
	 * 读取文件文本内容
	 * 
	 * @param path    文件路径，非空值。
	 * @param charset 字符集（默认UTF8）
	 * @return 文件内容，非空值。
	 * @throws IOException 读取失败
	 */
	public static String getText(File path, Charset charset) throws IOException {
		Objects.requireNonNull(path, "文件路径参数非空值");
		return getText(new FileInputStream(path), charset);
	}

	/**
	 * 读取文件文本内容
	 * 
	 * @param path    文件路径，非空。
	 * @param charset 字符集（默认UTF8）
	 * @return 文件内容，非空值。
	 * @throws IOException 读取失败
	 */
	public static String getText(Path path, Charset charset) throws IOException {
		Objects.requireNonNull(path, "文件路径参数非空值");
		return getText(Files.newInputStream(path), charset);
	}

	/**
	 * 读取字节流内容，读取结束后自动关闭字节流。
	 * 
	 * @param stream  字节流，非空值。
	 * @param charset 字符集（默认UTF8）
	 * @return 字节流内容，非空值。
	 * @throws IOException 读取失败
	 */
	public static String getText(InputStream stream, Charset charset) throws IOException {
		BufferedReader reader = new BufferedReader(
				new InputStreamReader(stream, charset != null ? charset : StandardCharsets.UTF_8));
		return getText(reader);
	}

	/**
	 * 读取字符流内容，读取结束后自动关闭字符流。
	 * 
	 * @param reader 字符流，非空值。
	 * @return 字符流内容，非空值。
	 * @throws IOException 读取失败
	 */
	public static String getText(Reader reader) throws IOException {
		StringBuilder answer = new StringBuilder();
		char[] charBuffer = new char[256];
		int nbCharRead;
		try {
			while ((nbCharRead = reader.read(charBuffer)) != -1) {
				answer.append(charBuffer, 0, nbCharRead);
			}
			Reader temp = reader;
			reader = null;
			temp.close();
		} finally {
			if (reader != null) {
				reader.close();
			}
		}
		return answer.toString();
	}

	public static File quietCheckPath(File home, File path) {
		if (home == null || path == null || path.isAbsolute()) {
			return path;
		}
		return new File(home, path.getPath());
	}

	public static File quietCheckPath(File home, String path) {
		return quietCheckPath(home, path, true);
	}

	public static File quietCheckPath(File home, String path, boolean check) {
		if (path == null || path.length() == 0) {
			return null;
		}
		if (home == null) {
			return new File(path);
		}
		if (check) {
			File file = new File(path);
			if (file.isAbsolute()) {
				return file;
			}
		}
		return new File(home, path);
	}

	public static boolean quietCheckParentPath(File file) {
		File pfile = file.getParentFile();
		if (pfile != null && !pfile.exists()) {
			return pfile.mkdirs() && pfile.exists();
		}
		return true;
	}

	public static boolean quietPathEquals(File f1, File f2) {
		if (f1 == null) {
			return f2 == null;
		} else {
			if (f2 == null) {
				return false;
			}
			try {
				File fa = f1.getCanonicalFile();
				File fb = f2.getCanonicalFile();
				return fa.equals(fb);
			} catch (IOException ex) {
				return false;
			}
		}
	}

	public static void quietClose(Closeable obj) {
		try {
			if (obj != null) {
				obj.close();
			}
		} catch (Throwable th) {
		}
	}

	public static BufferedReader openBufferedReader(File file, Charset charset) throws IOException {
		Objects.requireNonNull(file);
		return new BufferedReader(new InputStreamReader(new FileInputStream(file), charset != null ? charset : UTF_8));
	}

	public static BufferedReader openBufferedReader(String file, Charset charset) throws IOException {
		Objects.requireNonNull(file);
		if (file.startsWith("classpath:") && file.length() > 10) {
			String path = file.substring(10);
			InputStream in = ClassLoader.getSystemResourceAsStream(path);
			if (in == null) {
				IOUtils.class.getClassLoader();
				in = IOUtils.class.getResourceAsStream(path);
				if (in == null) {
					throw new FileNotFoundException("resource not exist - " + file);
				}
			}
			return new BufferedReader(new InputStreamReader(in, charset != null ? charset : UTF_8));
		}
		return new BufferedReader(new InputStreamReader(new FileInputStream(file), charset != null ? charset : UTF_8));
	}

	public static boolean copyFile(File sour, File dest, boolean force) throws IOException {
		Objects.requireNonNull(sour);
		if (dest == null || !sour.exists()) {
			return false;
		}
		File fromPath = sour.getCanonicalFile();
		File copyPath = dest.getCanonicalFile();
		if (fromPath.equals(copyPath)) {
			return false;
		}

		if (copyPath.exists()) {
			if (!force) {
				return false;
			}
			copyPath.delete();
			if (copyPath.exists()) {
				throw new IOException("copy file at to path delete failed - " + copyPath.getPath());
			}
		}

		if (!quietCheckParentPath(copyPath)) {
			throw new IOException("copy file at directory mkdirs failed - " + copyPath.getPath());
		}
		FileInputStream ifm = null;
		FileOutputStream ofm = null;
		FileChannel ichl = null;
		FileChannel ochl = null;
		try {
			ifm = new FileInputStream(fromPath);
			ofm = new FileOutputStream(copyPath);
			ichl = ifm.getChannel();
			ochl = ofm.getChannel();
			ochl.transferFrom(ichl, 0, ichl.size());
			ochl.close();
		} finally {
			quietClose(ichl);
			quietClose(ifm);

			quietClose(ofm);
		}
		return true;
	}

	public static boolean moveFile(File sour, File dest, boolean force) throws IOException {
		Objects.requireNonNull(sour);
		if (dest == null || !sour.exists()) {
			return false;
		}
		File fromPath = sour.getCanonicalFile();
		File copyPath = dest.getCanonicalFile();
		if (fromPath.equals(copyPath)) {
			return false;
		}

		if (sour.isDirectory()) {
			throw new IOException("rename file failed, sour path must not directory - " + copyPath.getPath());
		}

		if (copyPath.exists()) {
			if (!force) {
				return false;
			}
			copyPath.delete();
			if (copyPath.exists()) {
				throw new IOException("rename file failed, dest path delete failed - " + copyPath.getPath());
			}
		}

		if (!quietCheckParentPath(copyPath)) {
			throw new IOException("rename file failed, parent path mkdirs failed - " + copyPath.getPath());
		}
		if (!fromPath.renameTo(copyPath)) {
			// windows环境不能跨盘重命名，则先复制再删除
			if (copyFile(fromPath, copyPath, false)) {
				return fromPath.delete();
			}
			return false;
		}
		return true;
	}

}
